use std::net:: { TcpListener , TcpStream }; 
use std::io::prelude::*;
use std::time::Duration;
use std::thread;

use std::sync::{ Mutex, Arc };
use std::rc::Rc;

use std::sync::mpsc;

pub struct ThreadPool{
    workers : Vec<Worker>,
    sender : mpsc::Sender<Message>,
}

type Job = Box<dyn FnOnce() + Send + 'static>;

enum Message {
    NewJob(Job),
    Terminate,
}

impl ThreadPool{
    pub fn new(size : usize) -> ThreadPool{
        assert!(size > 0);
        let mut workers = Vec::with_capacity(size);

        let (tx,rx) = mpsc::channel();

        let rx = Arc::new(Mutex::new(rx));

        for index in 0..size {
            workers.push(Worker::new(index,rx.clone()));
        }

        ThreadPool{
            workers : workers,
            sender : tx,
        }
    }

    pub fn execute<F>(&self ,f:F) 
        where F : FnOnce() + Send + 'static
    {
        let job = Box::new(f);

        self.sender.send(Message::NewJob(job)).unwrap();
    }
}

impl Drop for ThreadPool {
    fn drop(&mut self){
        println!("Drop all!");
        for _ in &mut self.workers {
            self.sender.send(Message::Terminate);
        }

        for worker in &mut self.workers {
            println!("Shutting down worker {}", worker.id);

            if let Some(worker) = worker.thread.take() {
                worker.join().unwrap();
            }
        }
    }
}

struct Worker {
    id: usize,
    thread: Option<thread::JoinHandle<()>>,
}

impl Worker {
    fn new (id : usize , rx : Arc<Mutex<mpsc::Receiver<Message>>>) -> Worker {
        let thread = thread::spawn(move || {
            loop {
                let job = rx.lock().unwrap().recv().unwrap();
                match job {
                    Message::NewJob(job) =>{
                        println!("Worker {} got a job; executing.", id);
                        job();
                    },
                    Message::Terminate =>{
                        println!("Worker {} got a job; terminate.", id);
                        break;
                    },
                }
            }
        });

        Worker{
            id : id,
            thread : Some(thread),
        }
    }
}